// Pipeline Component Library Example
// ----------------------------------
// Colin Bowern (colinbowern@hotmail.com)
// January 2007
//
// Copyright (c) 2007 Colin Bowern
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// * The above copyright notice and this permission notice shall be included in
//   all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#region References
using Runtime = Microsoft.CommerceServer.Runtime;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
#endregion

namespace RockstarGuys.PipelineComponents
{
    /// <summary>
    /// Represents a collection of string keys and object values.
    /// </summary>
    /// <remarks>Keys are case-insensitive strings.</remarks>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1035:ICollectionImplementationsHaveStronglyTypedMembers"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
    [DebuggerDisplay("Count = {Count}"), Serializable]
    public class Dictionary : System.IDisposable, System.Collections.ICollection, System.Collections.IDictionary, System.Collections.IEnumerable
    {
        #region Fields
        private bool disposed;
        [NonSerialized]
        private Runtime.IDictionary dictionary;
        [NonSerialized]
        private object syncRoot;
        private int version;
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the <see cref="T:Object"/> with the specified key.
        /// </summary>
        /// <remarks>
        /// Setting a value to null will remove it from the dictionary.
        /// </remarks>
        /// <value></value>
        public virtual object this[string key]
        {
            get
            {
                Helper.CheckForNullReference(key, "key");

                object value = this.dictionary[key];
                return (Convert.IsDBNull(value) ? null : value);
            }
            set
            {
                Helper.CheckForNullReference(key, "key");

                this.dictionary[key] = value;
                this.version++;
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="Object"/> with the specified key.
        /// </summary>
        /// <value></value>
        public virtual object this[object key]
        {
            get
            {
                CheckTypeCompatibility(key, "key", typeof(string));
                return this[(string)key];
            }
            set
            {
                CheckTypeCompatibility(key, "key", typeof(string));
                this[(string)key] = value;
            }
        }

        /// <summary>
        /// Gets the number of elements contained in the <see cref="T:System.Collections.ICollection"></see>.
        /// </summary>
        /// <value></value>
        /// <returns>The number of elements contained in the <see cref="T:System.Collections.ICollection"></see>.</returns>
        public int Count
        {
            get { return this.InnerDictionary.Count; }
        }

        /// <summary>
        /// Gets the inner dictionary.
        /// </summary>
        /// <value>The inner dictionary.</value>
        [CLSCompliant(false)]
        public Runtime.IDictionary InnerDictionary
        {
            get { return this.dictionary; }
        }

        /// <summary>
        /// Gets a value indicating whether the <see cref="T:System.Collections.IDictionary"></see> object has a fixed size.
        /// </summary>
        /// <value></value>
        /// <returns>true if the <see cref="T:System.Collections.IDictionary"></see> object has a fixed size; otherwise, false.</returns>
        public bool IsFixedSize
        {
            get { return false; }
        }

        /// <summary>
        /// Gets a value indicating whether the <see cref="T:System.Collections.IDictionary"></see> object is read-only.
        /// </summary>
        /// <value></value>
        /// <returns>true if the <see cref="T:System.Collections.IDictionary"></see> object is read-only; otherwise, false.</returns>
        public bool IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// Gets an <see cref="T:System.Collections.ICollection"></see> object containing the keys of the <see cref="T:System.Collections.IDictionary"></see> object.
        /// </summary>
        /// <value></value>
        /// <returns>An <see cref="T:System.Collections.ICollection"></see> object containing the keys of the <see cref="T:System.Collections.IDictionary"></see> object.</returns>
        public ICollection Keys
        {
            get
            {
                List<string> keys = new List<string>(this.Count);
                foreach (string key in this)
                {
                    keys.Add(key);
                }
                return keys;
            }
        }

        /// <summary>
        /// Gets an <see cref="T:System.Collections.ICollection"></see> object containing the values in the <see cref="T:System.Collections.IDictionary"></see> object.
        /// </summary>
        /// <value></value>
        /// <returns>An <see cref="T:System.Collections.ICollection"></see> object containing the values in the <see cref="T:System.Collections.IDictionary"></see> object.</returns>
        public virtual ICollection Values
        {
            get
            {
                List<object> values = new List<object>(this.Count);
                foreach (string key in this)
                {
                    values.Add(this[key]);
                }
                return values;
            }
        }

        /// <summary>
        /// Gets a value
        /// indicating whether access to the <see cref="T:System.Collections.ICollection"/> is synchronized
        /// (thread-safe).
        /// </summary>
        /// <value></value>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
        bool System.Collections.ICollection.IsSynchronized
        {
            get { return false; }
        }

        /// <summary>
        /// When implemented by a class, gets an object that
        /// can be used to synchronize access to the <see cref="T:System.Collections.ICollection"/>.
        /// </summary>
        /// <value></value>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
        object System.Collections.ICollection.SyncRoot
        {
            get
            {
                if (this.syncRoot == null)
                {
                    Interlocked.CompareExchange(ref this.syncRoot, new object(), null);
                }
                return this.syncRoot;
            }
        }
        #endregion

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="Dictionary"/> class.
        /// </summary>
        /// <remarks>
        /// The underlying store can be persisted through referencing the InnerDictionary property.
        /// </remarks>
        public Dictionary() : this(null) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="Dictionary"/> class.
        /// </summary>
        /// <param name="dictionary">The dictionary.</param>
        /// <remarks>
        /// Uses the container parameter for the underlying store.  If it is null then
        /// a new instance of the store will be created.
        /// </remarks>
        [CLSCompliant(false)]
        public Dictionary(Runtime.IDictionary dictionary)
        {
            this.dictionary = (dictionary != null ? dictionary : new Runtime.DictionaryClass());
        }
        #endregion

        #region Deconstructors
        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations 
        /// before the <see cref="Dictionary"/>
        /// is reclaimed by garbage collection.
        /// </summary>
        ~Dictionary()
        {
            Dispose(false);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or
        /// resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Use this method to release the unmanaged resources used by this 
        /// object and optionally release the managed resources.
        /// </summary>
        /// <param name="disposing">True if this was called from the Dispose 
        /// method, False if called from the finalizer.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this.disposed)
            {
                this.disposed = true;

                // Release Managed Resources
                //if (disposing) { }

                // Release Unmanaged Resources
                if (this.dictionary != null)
                {
                    Marshal.ReleaseComObject(this.dictionary);
                    this.dictionary = null;
                }
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Adds the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        public virtual void Add(string key, object value)
        {
            Helper.CheckForNullReference(key, "key");

            this[key] = value;
        }

        /// <summary>
        /// Adds an element with the provided key and value to the <see cref="T:System.Collections.IDictionary"></see> object.
        /// </summary>
        /// <param name="key">The <see cref="T:System.Object"></see> to use as the key of the element to add.</param>
        /// <param name="value">The <see cref="T:System.Object"></see> to use as the value of the element to add.</param>
        /// <exception cref="T:System.ArgumentException">An element with the same key already exists in the <see cref="T:System.Collections.IDictionary"></see> object. </exception>
        /// <exception cref="T:System.ArgumentNullException">key is null. </exception>
        /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.IDictionary"></see> is read-only.-or- The <see cref="T:System.Collections.IDictionary"></see> has a fixed size. </exception>
        public void Add(object key, object value)
        {
            CheckTypeCompatibility(key, "key", typeof(string));

            this.Add((string)key, value);
        }

        /// <summary>
        /// Removes all elements from the <see cref="T:System.Collections.IDictionary"></see> object.
        /// </summary>
        /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.IDictionary"></see> object is read-only. </exception>
        public void Clear()
        {
            ICollection keys = this.Keys;
            foreach (string key in keys)
            {
                this.InnerDictionary[key] = null;
            }
        }

        /// <summary>
        /// Determines whether the <see cref="T:System.Collections.IDictionary"></see> object contains an element with the specified key.
        /// </summary>
        /// <param name="key">The key to locate in the <see cref="T:System.Collections.IDictionary"></see> object.</param>
        /// <returns>
        /// true if the <see cref="T:System.Collections.IDictionary"></see> contains an element with the key; otherwise, false.
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException">key is null. </exception>
        public bool Contains(object key)
        {
            CheckTypeCompatibility(key, "key", typeof(string));

            return ContainsKey((string)key);
        }

        /// <summary>
        /// Determines whether the specified key contains key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>
        /// 	<c>true</c> if the specified key contains key; otherwise, <c>false</c>.
        /// </returns>
        public bool ContainsKey(string key)
        {
            Helper.CheckForNullReference(key, "key");

            bool isFound = false;
            foreach (string item in this.InnerDictionary)
            {
                if (StringComparer.InvariantCultureIgnoreCase.Compare(key, item) == 0)
                {
                    isFound = true;
                    break;
                }
            }
            return isFound;
        }

        /// <summary>
        /// Determines whether the specified value contains value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>
        /// 	<c>true</c> if the specified value contains value; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool ContainsValue(object value)
        {
            if (value == null)
            {
                return false;
            }

            bool isFound = false;
            foreach (string key in this.InnerDictionary)
            {
                if (this[key].Equals(value))
                {
                    isFound = true;
                    break;
                }
            }
            return isFound;
        }

        /// <summary>
        /// Copies the elements of the <see cref="T:System.Collections.ICollection"></see> to an <see cref="T:System.Array"></see>, starting at a particular <see cref="T:System.Array"></see> index.
        /// </summary>
        /// <remarks>
        /// The order of keys is not guaranteed.
        /// </remarks>
        /// <param name="array">The one-dimensional <see cref="T:System.Array"></see> that is the destination of the elements copied from <see cref="T:System.Collections.ICollection"></see>. The <see cref="T:System.Array"></see> must have zero-based indexing.</param>
        /// <param name="index">The zero-based index in array at which copying begins.</param>
        /// <exception cref="T:System.ArgumentNullException">array is null. </exception>
        /// <exception cref="T:System.ArgumentException">The type of the source <see cref="T:System.Collections.ICollection"></see> cannot be cast automatically to the type of the destination array. </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">index is less than zero. </exception>
        /// <exception cref="T:System.ArgumentException">array is multidimensional.-or- index is equal to or greater than the length of array.-or- The number of elements in the source <see cref="T:System.Collections.ICollection"></see> is greater than the available space from index to the end of the destination array. </exception>
        public virtual void CopyTo(Array array, int index)
        {
            Helper.CheckForNullReference(array, "array");
            Helper.CheckRange<int>(array.Rank, "array.Rank", 1, 1);
            Helper.CheckRange<int>(index, "index", 0, array.Length);
            Helper.CheckRange<int>(this.Count, "Count", 0, array.Length - index);

            foreach (string key in this)
            {
                KeyValuePair<string, object> keyValuePair = new KeyValuePair<string, object>(key, this[key]);
                try
                {
                    array.SetValue(keyValuePair, index++);
                }
                catch (InvalidCastException)
                {
                    throw new ArgumentTypeException("array", typeof(KeyValuePair<string, object>));
                }
            }
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"></see> is equal to the current <see cref="T:System.Object"></see>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"></see> to compare with the current <see cref="T:System.Object"></see>.</param>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"></see> is equal to the current <see cref="T:System.Object"></see>; otherwise, false.
        /// </returns>
        public override bool Equals(object obj)
        {
            Dictionary value = obj as Dictionary;
            bool isEqual = false;
            if (value != null)
            {
                isEqual = this.InnerDictionary.Equals(value.InnerDictionary);
            }
            else
            {
                isEqual = base.Equals(obj);
            }
            return isEqual;
        }

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"></see>.
        /// </returns>
        public override int GetHashCode()
        {
            return this.InnerDictionary.GetHashCode();
        }

        /// <summary>
        /// Gets the enumerator.
        /// </summary>
        /// <returns></returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2116:AptcaMethodsShouldOnlyCallAptcaMethods")]
        public System.Collections.IEnumerator GetEnumerator()
        {
            return this.dictionary.GetEnumerator();
        }

        IDictionaryEnumerator IDictionary.GetEnumerator()
        {
            return new Dictionary.Enumerator(this);
        }

        /// <summary>
        /// Gets the GUID from string value.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public Guid? GetGuidFromStringValue(string key)
        {
            Helper.CheckForNullReference(key, "key");
            string value = this[key] as string;

            Guid? guid;
            Guid guidValue;
            if ((value != null) && Helper.GuidTryParse(value, out guidValue))
            {
                guid = new Nullable<Guid>(guidValue);
            }
            else
            {
                guid = new Nullable<Guid>();
            }
            return guid;
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public TValue GetValue<TValue>(string key)
        {
            Helper.CheckForNullReference(key, "key");
            Type type = typeof(TValue);

            object value = this[key];

            if (type.IsValueType && (value == null) || (Convert.IsDBNull(value)))
            {
                value = default(TValue);
            }
            else
            {
                CheckTypeCompatibility(value, "value", type);
            }

            return (TValue)value;
        }

        /// <summary>
        /// Removes the element with the specified key from the <see cref="T:System.Collections.IDictionary"></see> object.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public bool Remove(string key)
        {
            Helper.CheckForNullReference(key, "key");

            bool isRemoved = false;
            if (this.ContainsKey(key))
            {
                this[key] = null;
                isRemoved = true;
            }
            return isRemoved;
        }

        /// <summary>
        /// Removes the element with the specified key from the <see cref="T:System.Collections.IDictionary"></see> object.
        /// </summary>
        /// <param name="key">The key of the element to remove.</param>
        /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.IDictionary"></see> object is read-only.-or- The <see cref="T:System.Collections.IDictionary"></see> has a fixed size. </exception>
        /// <exception cref="T:System.ArgumentNullException">key is null. </exception>
        public void Remove(object key)
        {
            CheckTypeCompatibility(key, "key", typeof(string));

            this.Remove((string)key);
        }

        /// <summary>
        /// Tries to get the value.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public bool TryGetValue<TValue>(string key, out TValue value)
        {
            Helper.CheckForNullReference(key, "key");
            Type type = typeof(TValue);

            object innerValue = this[key];

            bool isValid;
            if ((innerValue != null) && (!Convert.IsDBNull(innerValue)) && IsTypeCompatible(innerValue, type))
            {
                value = (TValue)innerValue;
                isValid = true;
            }
            else
            {
                value = default(TValue);
                isValid = false;
            }
            return isValid;
        }

        #region Protected Methods
        /// <summary>
        /// Checks the type compatibility.
        /// </summary>
        /// <param name="variable">The variable.</param>
        /// <param name="variableName">Name of the variable.</param>
        /// <param name="type">The type.</param>
        protected static void CheckTypeCompatibility(object variable, string variableName, Type type)
        {
            if (!IsTypeCompatible(variable, type))
            {
                throw new ArgumentTypeException(variableName);
            }
        }

        /// <summary>
        /// Determines whether [is type compatible] [the specified variable].
        /// </summary>
        /// <param name="variable">The variable.</param>
        /// <param name="type">The type.</param>
        /// <returns>
        /// 	<c>true</c> if [is type compatible] [the specified variable]; otherwise, <c>false</c>.
        /// </returns>
        protected static bool IsTypeCompatible(object variable, Type type)
        {
            Helper.CheckForNullReference(type, "type");
            bool isTypeCompatible = false;

            // If the type is a value type then rule it out right away if it is
            // a null value.  Otherwise check to see if it is an instance of
            // or assignable to the target type.
            if ((!type.IsValueType && ((variable == null) || Convert.IsDBNull(variable))) ||
                (variable != null && (type.IsInstanceOfType(variable) || type.IsAssignableFrom(variable.GetType()))))
            {
                isTypeCompatible = true;
            }

            return isTypeCompatible;
        }
        #endregion
        #endregion

        #region Nested Types
        /// <summary>
        /// Enumerates over the values in the simple list.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1038:EnumeratorsShouldBeStronglyTyped"), Serializable, StructLayout(LayoutKind.Sequential)]
        public struct Enumerator : IDictionaryEnumerator
        {
            #region Fields
            [NonSerialized]
            private Dictionary dictionary;
            private int index;
            private int version;
            private IList<string> keys;
            #endregion

            #region Properties
            /// <summary>
            /// Gets the current object.
            /// </summary>
            /// <value>The current.</value>
            public object Current
            {
                get { return Value; }
            }

            /// <summary>
            /// Gets the key.
            /// </summary>
            /// <value>The key.</value>
            public object Key
            {
                get { return this.keys[index]; }
            }

            /// <summary>
            /// Gets the value.
            /// </summary>
            /// <value>The value.</value>
            public object Value
            {
                get { return this.dictionary[Key]; }
            }

            /// <summary>
            /// Gets the entry.
            /// </summary>
            /// <value>The entry.</value>
            public DictionaryEntry Entry
            {
                get { return new DictionaryEntry(Key, Value); }
            }
            #endregion

            #region Constructors
            /// <summary>
            /// Initializes a new instance of the <see cref="Enumerator"/> class.
            /// </summary>
            /// <param name="dictionary">The dictionary.</param>
            internal Enumerator(Dictionary dictionary)
            {
                this.dictionary = dictionary;
                this.keys = (IList<string>)dictionary.Keys;
                this.index = -1;
                this.version = dictionary.version;
            }
            #endregion

            #region Methods
            /// <summary>
            /// Advances the enumerator to the next element of the collection.
            /// </summary>
            /// <returns>
            /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
            /// </returns>
            /// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
            public bool MoveNext()
            {
                if (this.version != this.dictionary.version)
                {
                    throw new InvalidOperationException();
                }

                bool isValid = false;
                if (this.index < this.dictionary.Count - 1)
                {
                    this.index++;
                    isValid = true;
                }
                return isValid;
            }

            /// <summary>
            /// Sets the enumerator to its initial position, which is before the first element in the collection.
            /// </summary>
            /// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
            void IEnumerator.Reset()
            {
                if (this.version != this.dictionary.version)
                {
                    throw new InvalidOperationException();
                }

                this.index = -1;
            }
            #endregion
        }
        #endregion
    }
}
